package daobeauty.interceptor.invoke.impl;

import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.SessionFactory;

import daobeauty.interceptor.invoke.AbstractCurrentSessionInvokeStrategy;
import daobeauty.interceptor.invoke.ArgumentMask;
import daobeauty.interceptor.invoke.fill.ArrayQueryFill;
import daobeauty.interceptor.invoke.fill.CollectionQueryFill;
import daobeauty.interceptor.invoke.fill.EmptyFill;
import daobeauty.interceptor.invoke.fill.MapQueryFill;
import daobeauty.interceptor.invoke.fill.PlainQueryFill;
import daobeauty.interceptor.invoke.fill.ParameterFillStrategy;
import daobeauty.interceptor.returntype.executor.ExecutionStrategy;
import daobeauty.interceptor.returntype.executor.criteria.CriteriaExecutor;
import daobeauty.interceptor.returntype.executor.query.QueryExecutor;
import daobeauty.util.MethodSignatureUtils;

public class FindAllInvoke extends AbstractCurrentSessionInvokeStrategy {
	private static final Log LOG = LogFactory.getLog(FindAllInvoke.class);
	
	private final ExecutionStrategy executor;
	private final ArgumentMask argumentMask;
	private final ParameterFillStrategy fillStrategy;
	private final QueryOrCriteriaObtainStartegy queryStrategy;
	
	@SuppressWarnings("unchecked")
	public FindAllInvoke(Method method, SessionFactory sessionFactory, Class domainClass) {
		super(sessionFactory, domainClass);
		final Class [] parameterTypes = method.getParameterTypes();
		// case public List findAll();
		if (parameterTypes==null || parameterTypes.length<1){
			fillStrategy = EmptyFill.INSTANCE;
			argumentMask = new ArgumentMask(method, 0);
			queryStrategy = new DefaultCriteriaStrategy();
			executor = new CriteriaExecutor(method.getReturnType());
		}
		// case public List findAll(String hqlQuery ...);
		else if (parameterTypes[0]!=null && String.class.isAssignableFrom(parameterTypes[0])){
			executor = new QueryExecutor(method.getReturnType());
			queryStrategy = new FirstArgumentQueryStrategy();
			// case public List findAll(String hqlQuery);
			if (parameterTypes.length==1){
				fillStrategy = EmptyFill.INSTANCE;
				argumentMask = new ArgumentMask(method, 1);
			}
			// case public List findAll(String hqlQuery, Map valuesByName);
			else if (parameterTypes[1]!=null && Map.class.isAssignableFrom(parameterTypes[1])){
				fillStrategy = new MapQueryFill();
				argumentMask = new ArgumentMask(method, 1);
			}
			// case public List findAll(String hqlQuery, Collection values);
			else if (parameterTypes[1]!=null && Collection.class.isAssignableFrom(parameterTypes[1])){
				fillStrategy = new CollectionQueryFill();
				argumentMask = new ArgumentMask(method, 1);
			}
			// case public List findAll(String hqlQuery, Object [] values);
			else if (parameterTypes[1]!=null && Object[].class.isAssignableFrom(parameterTypes[1])){
				fillStrategy = new ArrayQueryFill();
				argumentMask = new ArgumentMask(method, 1);
			}
			// cases public List findAll(String hqlQuery, Object parameter);
			// cases public List findAll(String hqlQuery, Object parameter1, Object parameter2, ...);
			else{
				fillStrategy = new PlainQueryFill();
				argumentMask = new ArgumentMask(method, 1);
			}
		}
		else{
			throw new IllegalArgumentException("findAll found with a non-string argument at first place, where a hibernate query was expected");
		}
		if (LOG.isInfoEnabled()){
			LOG.info("created findAll strategy with arguments for "+(fillStrategy instanceof MapQueryFill?"map of named ":fillStrategy.toString())+" parameters "+(argumentMask.hasLimiting()?", limiting":"")+(argumentMask.hasSorting()?", sorting":"")+" for method "+MethodSignatureUtils.toMethodSignature(method));
		}
	}

	public Object invoke(String methodName, Object[] args) throws Exception {
		final Object queryOrCriteria = queryStrategy.getQueryOrCriteria(args);
		fillStrategy.fillParameters(queryOrCriteria, argumentMask.getFiltered(args));
		return executor.execute(queryOrCriteria, argumentMask.getLimiting(args), argumentMask.getSorting(args));
	}
	
	
	
	private interface QueryOrCriteriaObtainStartegy{
		public Object getQueryOrCriteria(Object [] parameters);
	}
	
	private class DefaultCriteriaStrategy implements QueryOrCriteriaObtainStartegy{
		public Object getQueryOrCriteria(Object[] parameters) {
			return createCriteria();
		}
	} 
	
	private class FirstArgumentQueryStrategy implements QueryOrCriteriaObtainStartegy{
		public Object getQueryOrCriteria(Object[] parameters) {
			return getCurrentSession().createQuery((String) parameters[0]);
		}
	}
}
